package mbsl.ui;

import mbsl.util.SWTUtils;
import static mbsl.util.SWTUtils.Direction.*;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Scale;

public class RatingPanel {
	/**
	 * Creates a panel with a check box, a scale, and a text field:
	 * <p>
	 * The check box is used to turn the other two components on or off. If you
	 * don't want to set a rating, you uncheck the check box. The scale sets and
	 * shows the value (or rating). The text field shows the value (or rating)
	 * in a textual form. Double-clicking on the control will activate the
	 * scale.
	 * <p>
	 * The default selected rating will be (min + (max - min) / 2)
	 * 
	 * @see #RatingPanel(Composite, int, int, int)
	 * @see #RatingPanel(Composite, int, int, double, int)
	 * @param pParent
	 *            The parent Composite to which this component should be
	 *            attached.
	 * @param pMinRating
	 *            The lowest number on the scale. A negative value will be
	 *            changed to 0.
	 * @param pMaxRating
	 *            The highest number on the scale. If this < min rating, it will
	 *            be changed to min rating.
	 */
	public RatingPanel(Composite pParent, int pMinRating, int pMaxRating) {
		this(pParent, pMinRating, pMaxRating, DEFAULT);
	}

	/**
	 * Creates a panel with a check box, a scale, and a text field:
	 * <p>
	 * The check box is used to turn the other two components on or off. If you
	 * don't want to set a rating, you uncheck the check box. The scale sets and
	 * shows the value (or rating). The text field shows the value (or rating)
	 * in a textual form. Double-clicking on the control will activate the
	 * scale.
	 * 
	 * @see #RatingPanel(Composite, int, int)
	 * @see #RatingPanel(Composite, int, int, double, int)
	 * @param pParent
	 *            The parent Composite to which this component should be
	 *            attached.
	 * @param pMinRating
	 *            The lowest number on the scale. A negative value will be
	 *            changed to 0.
	 * @param pMaxRating
	 *            The highest number on the scale. If this < min rating, it will
	 *            be changed to min rating.
	 * @param pDefaultRating
	 *            The default setting of the scale. If this is not within [min
	 *            rating, max rating], it will be set to the default (min + (max
	 *            - min) / 2). You can also pass {@link #DEFAULT} to get this
	 *            behaviour.
	 */
	public RatingPanel(Composite pParent, int pMinRating, int pMaxRating, int pDefaultRating) {
		this(pParent, pMinRating, pMaxRating, pDefaultRating, 1);
	}

	/**
	 * Creates a panel with a check box, a scale, and a text field:
	 * <p>
	 * The check box is used to turn the other two components on or off. If you
	 * don't want to set a rating, you uncheck the check box. The scale sets and
	 * shows the value (or rating). The text field shows the value (or rating)
	 * in a textual form. Double-clicking on the control will activate the
	 * scale.
	 * <p>
	 * The divisor argument sets the granularity of the scale, whose effects are
	 * best explained by example. Let's assume a minimum rating of 0, and a
	 * maximum rating of 5:
	 * <ul>
	 * <li>A divisor of 1 (the default) will give you the option set [0, 1, ...,
	 * 5] (a total of 6 steps on the scale).</li>
	 * <li>A divisor of 2 will give you the option set [0, 0.5, 1, ..., 5] (a
	 * total of 12 steps).</li>
	 * <li>A divisor of 10 will give you the option set [0, 0.1, 0.2, ..., 5] (a
	 * total of 60 steps).</li>
	 * </ul>
	 * Note that only values between 1 and 10 are allowed, and the label will
	 * only show one decimal. The system will work best if the value is in the
	 * set [1, 2, 5, 10] (numbers 10 are divisible with), and 3 should work OK
	 * as well.
	 * 
	 * @see #RatingPanel(Composite, int, int)
	 * @see #RatingPanel(Composite, int, int, int)
	 * @param pParent
	 *            The parent Composite to which this component should be
	 *            attached.
	 * @param pMinRating
	 *            The lowest number on the scale. A negative value will be
	 *            changed to 0.
	 * @param pMaxRating
	 *            The highest number on the scale. If this < min rating, it will
	 *            be changed to min rating.
	 * @param pDefaultRating
	 *            The default setting of the scale. If this is not within [min
	 *            rating, max rating], it will be set to the default (min + (max
	 *            - min) / 2). You can also pass {@link #DEFAULT} to get this
	 *            behaviour.
	 * @param pDivisor
	 *            The divisor used to determine the granularity of the scale. A
	 *            value above 10 will be divided by 10 until it's within range,
	 *            and values less than 1 will be 1.
	 */
	public RatingPanel(Composite pParent, int pMinRating, int pMaxRating, double pDefaultRating, int pDivisor) {
		mPanel = new Composite(pParent, SWT.NONE);

		if (pMinRating < 0)
			pMinRating = 0;

		if (pMaxRating < pMinRating)
			pMaxRating = pMinRating;

		if (pDefaultRating == DEFAULT || pDefaultRating < pMinRating || pDefaultRating > pMaxRating)
			pDefaultRating = (int)Math.ceil(pMinRating + (pMaxRating - pMinRating) / (double)2);

		if (pDivisor < 1)
			pDivisor = 1;
		else
			while (pDivisor > 10)
				pDivisor = pDivisor / 10;

		mMinRating = pMinRating * pDivisor;
		mMaxRating = pMaxRating * pDivisor;
		mDefaultRating = (int)(pDefaultRating * pDivisor);
		mDivisor = pDivisor;

		setUpComponents();
	}
	/** Returns the Composite holding the components of this panel. */
	public Composite getWidget() {
		return mPanel;
	}
	/** Returns the value of the Scale, or null if the Scale is disabled */
	public Double getValue() {
		return mCheckBox.getSelection() ? mScale.getSelection() / (double)mDivisor : null;
	}
	/**
	 * Sets the value of the Scale, or deactivates it if null is passed
	 * <p>
	 * If the value passed is lesser than min rating, it will be set to min
	 * rating. If the value is greater than max rating, it will be set to max
	 * rating.
	 */
	public void setValue(Double pValue) {
		if (pValue == null) {
			activateControls(false);
			return;
		}
		
		if (pValue < mMinRating)
			pValue = (double)mMinRating;
		else if (pValue > mMaxRating)
			pValue = (double)mMaxRating;
		
		mScale.setSelection((int)(pValue * mDivisor));

		activateControls(true);
	}
	/** Sets up the components */
	private void setUpComponents() {
		mPanel.addMouseListener(new MouseAdapter() {		// If the Scale is disabled,
			@Override										// mouse gestures shine
			public void mouseDoubleClick(MouseEvent pE) {	// through to the under
				activateControls(true);						// laying container, so the
			}												// activation listener must
		});													// be placed here.
		mPanel.setLayout(new FormLayout());

		createCheckBox();
		createScale();
		
		createLabel(); // the Label relies on the Scale being set up first, for it to be able to determine its own size and position.
	}
	/** Sets up the check box */
	private void createCheckBox() {
		mCheckBox = new Button(mPanel, SWT.CHECK);
		
		mCheckBox.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 0),
				SWTUtils.getMeta(down, 100, 0),
				SWTUtils.getMeta(left, 0, 0),
				SWTUtils.getMeta(right, 0, 15)
		));
		
		mCheckBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent pE) {
				activateControls(mCheckBox.getSelection());
			}
		});
	}
	/** Sets up the scale */
	private void createScale() {
		mScale = new Scale(mPanel, SWT.HORIZONTAL);
		
		FormData sliderLayout = SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 0),
				SWTUtils.getMeta(down, 100, 0),
				SWTUtils.getMeta(left, 0, 15),
				SWTUtils.getMeta(right, 100, -20)
		);
/*		mScale.addMouseMoveListener(new MouseMoveListener() {
			@Override
			public void mouseMove(MouseEvent pE) {
				System.out.println(((Scale)pE.getSource()).getBounds().width);
				System.out.println(pE);
			}
		});
*/		
		
		mScale.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent pE) {
				updateLabelText();
			}
		});
		mScale.setEnabled(false);
		mScale.setLayoutData(sliderLayout);
		mScale.setMaximum(mMaxRating);
		mScale.setMinimum(mMinRating);
		mScale.setSelection(mDefaultRating);
	}
	/** Sets up the label */
	private void createLabel() {
		mLabel = new Label(mPanel, SWT.CENTER);
		
		mLabel.setText("");
		mLabel.setAlignment(SWT.CENTER);
		mLabel.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, (SWTUtils.getSize(mScale).y - SWTUtils.getSize(mLabel).y) / 2),
				SWTUtils.getMeta(left, 100, -20),
				SWTUtils.getMeta(right, 100, 0)
		));
	}
	/** Updates the label text */
	private void updateLabelText() {
		if (!mScale.isEnabled())
			mLabel.setText("");
		else if (mDivisor == 1)
			mLabel.setText(String.format("%d", mScale.getSelection()));
		else
			mLabel.setText(String.format("%.1f", mScale.getSelection() / (double)mDivisor));
	}
	/** Activates/deactivates the controls */
	private void activateControls(boolean pDoActivate) {
		mCheckBox.setSelection(pDoActivate);
		mScale.setEnabled(pDoActivate);
		
		updateLabelText();
	}
	
	private Composite mPanel;
	private int mMinRating;
	private int mMaxRating;
	private int mDefaultRating;
	private int mDivisor;
	private Button mCheckBox;
	private Label mLabel;
	private Scale mScale;
	
	public static final int DEFAULT = -1;
}
